use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
    time::Instant,
};

use serde::Serialize;
use sysinfo::{NetworkExt, System, SystemExt};

#[derive(Debug, Serialize, Clone)]
pub struct NicStats {
    pub name: String,
    pub mac: Option<String>,
    pub ipv4: Vec<String>,
    pub ipv6: Vec<String>,
    pub received: u64,
    pub transmitted: u64,
    pub rx_bytes_per_sec: f64,
    pub tx_bytes_per_sec: f64,
}

#[derive(Clone)]
pub struct NicMonitor {
    inner: Arc<Mutex<NicMonitorInner>>,
}

struct NicMonitorInner {
    system: System,
    last: HashMap<String, NicSnapshot>,
    interfaces: HashMap<String, InterfaceInfo>,
}

#[derive(Clone)]
struct NicSnapshot {
    received: u64,
    transmitted: u64,
    instant: Instant,
}

impl NicMonitor {
    pub fn new() -> Self {
        let mut system = System::new_all();
        system.refresh_networks_list();
        system.refresh_networks();

        let mut inner = NicMonitorInner {
            system,
            last: HashMap::new(),
            interfaces: HashMap::new(),
        };
        inner.refresh_interfaces();

        Self {
            inner: Arc::new(Mutex::new(inner)),
        }
    }

    pub fn collect_stats(&self) -> Vec<NicStats> {
        let mut inner = self.inner.lock().expect("NicMonitor mutex poisoned");
        inner.collect_stats()
    }
}

impl Default for NicMonitor {
    fn default() -> Self {
        Self::new()
    }
}

impl NicMonitorInner {
    fn collect_stats(&mut self) -> Vec<NicStats> {
        self.refresh_interfaces();

        self.system.refresh_networks_list();
        self.system.refresh_networks();

        let now = Instant::now();
        let mut stats: Vec<NicStats> = Vec::new();

        for (name, data) in self.system.networks() {
            let received = data.received();
            let transmitted = data.transmitted();

            let interface = self.interfaces.get(name).cloned().unwrap_or_default();

            let (rx_bytes_per_sec, tx_bytes_per_sec) = if let Some(snapshot) = self.last.get(name) {
                let elapsed = now.duration_since(snapshot.instant).as_secs_f64();
                if elapsed > 0.0 {
                    (
                        (received.saturating_sub(snapshot.received)) as f64 / elapsed,
                        (transmitted.saturating_sub(snapshot.transmitted)) as f64 / elapsed,
                    )
                } else {
                    (0.0, 0.0)
                }
            } else {
                (0.0, 0.0)
            };

            self.last.insert(
                name.clone(),
                NicSnapshot {
                    received,
                    transmitted,
                    instant: now,
                },
            );

            stats.push(NicStats {
                name: name.clone(),
                mac: interface.mac,
                ipv4: interface.ipv4,
                ipv6: interface.ipv6,
                received,
                transmitted,
                rx_bytes_per_sec,
                tx_bytes_per_sec,
            });
        }

        stats.sort_by(|a, b| a.name.cmp(&b.name));
        stats
    }

    fn refresh_interfaces(&mut self) {
        let mut map = HashMap::new();
        refresh_interfaces_via_os(&mut map);
        self.interfaces = map;
    }
}

#[derive(Clone, Default)]
struct InterfaceInfo {
    mac: Option<String>,
    ipv4: Vec<String>,
    ipv6: Vec<String>,
}

#[cfg(windows)]
fn refresh_interfaces_via_os(map: &mut HashMap<String, InterfaceInfo>) {
    use ipconfig::get_adapters;
    use std::net::IpAddr;

    if let Ok(adapters) = get_adapters() {
        for adapter in adapters {
            let name = if adapter.friendly_name().is_empty() {
                adapter.description()
            } else {
                adapter.friendly_name()
            };

            let entry = map
                .entry(name.to_string())
                .or_insert_with(InterfaceInfo::default);

            entry.mac = entry.mac.take().or_else(|| {
                adapter
                    .physical_address()
                    .filter(|addr| !addr.is_empty())
                    .map(|addr| {
                        addr.iter()
                            .map(|b| format!("{:02X}", b))
                            .collect::<Vec<_>>()
                            .join(":")
                    })
            });

            for addr in adapter.ip_addresses() {
                match addr {
                    IpAddr::V4(v4) => {
                        let ip = v4.to_string();
                        if !entry.ipv4.contains(&ip) {
                            entry.ipv4.push(ip);
                        }
                    }
                    IpAddr::V6(v6) => {
                        let ip = v6.to_string();
                        if !entry.ipv6.contains(&ip) {
                            entry.ipv6.push(ip);
                        }
                    }
                }
            }
        }
    }
}

#[cfg(not(windows))]
fn refresh_interfaces_via_os(map: &mut HashMap<String, InterfaceInfo>) {
    let _ = map;
}
